home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / ec_plus / ec-plus.lha / EC++.TUTORIAL < prev    next >
Text File  |  1989-10-10  |  27KB  |  682 lines

  1.                 A TUTORIAL INTRODUCTION TO
  2.                     EC++: EXTENDED C++
  3.  
  4.                    by Glauco Masotti(*)
  5.  
  6.          University of Southern California, Los Angeles
  7.  
  8.                      September, 1989.
  9.  
  10.  
  11. (*) The Author is also with Ferrari Engineering, Modena, Italy.
  12.  
  13.  
  14.  
  15. SUMMARY.
  16.  
  17.    EC++ stands for "Extended C++" and is an attempt to overcome some of the
  18. difficulties encountered working with C++, to make programming large systems
  19. somewhat more enjoyable. EC++ implements some extensions to the C++ language and
  20. provides an environment to support:
  21.  - assertion checking, in the form of function preconditions and postconditions,
  22. and class invariants;
  23.  - parameterized classes;
  24.  - exception handling;
  25.  - garbage collection.  
  26.    The extensions to the language are translated by a preprocessor (which is
  27. itself called EC++) into plain C++. The overall environment, is set up with an
  28. additional small number of files that define some required macros and classes
  29. that will be used by the applications.
  30.  
  31.  
  32.  
  33. MOTIVATIONS: PROBLEMS AND WEAKNESSES OF C++.
  34.  
  35.  
  36. Assertions.
  37.  
  38.    Assertions are essentially logic statements that should be be verified at
  39. certains points in the code. Examples are invariants that must be satisfied by
  40. objects of a class at all times; preconditions that must be satified whenever
  41. a routine (function of any type) is called; postconditions that must
  42. guaranteed to be true after routine completion. A good presentation of the use
  43. of assertions can be found in [2]. No support is provided in C++ to express
  44. assertions in a disciplined way, and no emphasis has been put on them by other
  45. languages, except Eiffel.
  46.    However, judging on my past experience with large software projects, and
  47. the present experience with EC++, assertions are very useful to write correct
  48. and robust software. Assertions have to be considered as an integral part of
  49. the source code.
  50.    First of all, they play the role of documentation of the contract that
  51. controls the relationship between a function and its users: the precondition
  52. binds the users, the postcondition binds the function.  This make clear what
  53. are the assumed working conditions for a function and whose responsibility it
  54. is to provide or ascertain required conditions. Making software coherent with
  55. the specified assertions therefore will require the necessary checks, but also
  56. eliminates wildly diffuse checking tests thoughout the code, which is what
  57. happens when the contract is not specified.
  58.    Second, they enable us to express formal properties of classes and
  59. routines. This use of assertions may help particularly in the high-level
  60. design phase of a system, in combination with the use of abstract or virtual
  61. functions. Details of implementation will be provided only later, but
  62. requirements and properties of fundamental classes and relevant effects of
  63. functions can be defined, through assertions, at earlier stages of design.
  64.    Third, by monitoring assertions at run-time we can verify the correctness
  65. of our code. Many programming errors are pointed out immediately by violations
  66. of some assertion. Therefore we can save considerable time in testing and
  67. debugging.
  68.    Fourth, assertions are a means for guaranteeing coherence among different
  69. modules of a large program. We can use them to ensure non contradictory
  70. results of different functions and proper working conditions for each module.
  71.    
  72.  
  73. Libraries, reusable modules and parameterized types.
  74.  
  75.    While it is relatively easy in C++ to write functions of classes that
  76. depend on a single underlying type, e.g. the case of class Triangle and class
  77. Rectangle that inherit from a class Polygon, it is, however difficult to write
  78. functions that depend only on the general concepts of a container (sets,
  79. vectors, lists, tables, etc.) rather than on properties of individual
  80. containers [3].
  81.    This is the basic weakness that prevents writing more general reusable
  82. libraries and modules in C++. If polymorphic parameterized classes were
  83. available, these classes could be written just once, and instantiated to the
  84. actual needed types to provide code sharing and reusability.
  85.    C++ may fake parameterized types using macros ([1] par. 7.3.5), but this
  86. approach doesn't work well except on a small scale. In fact lines of thousands
  87. of characters are generated, by the standard C++ preprocessor, even for simple
  88. classes. Moreover use of macros is cumbersome in discovering compile errors
  89. and in debugging.
  90.    Another approach suggested for use of the GNU C++ Library [5], still places
  91. some burden on the programmer who wants to use generic classes. It also fails
  92. to reuse code, as code is duplicated, instead of resorting to the technique of
  93. using generic interfaces to a common implementation as suggested in [1].
  94.  
  95.  
  96. Exception handling.
  97.  
  98.    During program execution a function may encounter a situation where an
  99. error occurs or something strange happens. If the problem is detected, some
  100. attempt to recover a manageable situation may be done, or otherwise a failure
  101. should be reported. If the problem is not detected or ignored the program may
  102. either crash or report incorrect results. In the most lucky cases, things may
  103. be patched up locally, but in many cases the routine that detects the problem
  104. cannot cope with the abnormal situation locally; an exception detected in one
  105. context may require giving up the current activity and performing some action
  106. in another context.
  107.    A number of ad hoc techniques have been until now devised to deal with
  108. exceptional conditions: use of status variables, returning error values back
  109. along the chain of function calls, use of longjmp to reach a context where
  110. things can be recovered directly. This may involve restoring certain
  111. conditions and the problem is often complicated by memory management. In
  112. particular in C++ we have the problem of calling the destructors for all
  113. created and no more needed objects [3].
  114.    In any case coping with a particular kind of exception is tedious but
  115. feasible, coping with all possible exceptions leads to an explosion in code
  116. size and complexity, and is itself an error-prone activity, that is moreover
  117. difficult to test and debug, for the intrinsic reason that exceptions are
  118. usually unlikely situations. Not coping with exceptions (as is too often done)
  119. leads to programs that crash.  Most of the popular languages however, as well
  120. as C++, don't offer any exception handling mechanism or disciplined procedure.
  121.  
  122.  
  123. Automatic storage management.
  124.  
  125.    C++ inherits its memory management scheme from C. Storage for static and
  126. automatic objects is managed automatically, but object allocated in the free
  127. store must be explicitly deallocated, before the space can be reused. Manual
  128. memory management usually provides efficiency in time and space but is a
  129. burden for the programmer since it increases considerably the complexity of
  130. code and is a common source of tricky bugs.
  131.    Automatic garbage collection has not been put as part of the language in
  132. the belief that it is too much expensive. Some schemes indeed, need to
  133. maintain additional information in order to do garbage collection. Therefore
  134. they place an overhead, both in time and space, in an executing program; the
  135. representation of an object in memory takes more space, and accessing the
  136. objects takes more time than in a language without automatic garbage
  137. collection.
  138.    However as Boehm has demonstrated in his work [4], there are also
  139. conservative schemes that don't need any particular assumption, and that don't
  140. pose serious efficiency problems. The most pertinent observation here is that
  141. as long as we can tolerate virtual memory, we should tolerate automatic
  142. garbage collection as well.
  143.    Run-time and memory utilization however are not the only considerations to
  144. have in mind.  If done at a reasonable cost, automatic garbage collection can
  145. yield substantial benefits in design and development time, as well in
  146. debugging and maintenance effort.  It can also be a necessary key feature, in
  147. the design of general reusable modules, and in simplifying the exception
  148. handling problem.  In fact in the former case without a system taking care of
  149. memory management automatically it would not be possible to put responsibility
  150. for it in either the general modules or their users, and in the latter case it
  151. would not be possible to call all the necessary destructors for the objects
  152. created to cope with an abnormal situation, as seen before.  However if
  153. freeing memory is all that destructors have to do (as is often the case), with
  154. automatic garbage collection we don't need to care about the problem.
  155.  
  156.  
  157.  
  158. THE EC++ SOLUTION.
  159.  
  160.  
  161. Assertions.
  162.  
  163.    In EC++ we can express assertions in the form of class invariants and
  164. function pre and post conditions.  A class invariant is defined like any other
  165. member function, but it uses the name "invariant", which is a reserved word in
  166. EC++. Function pre and post conditions must be specified after the declaration
  167. of a function and prior to its body.
  168.    A function precondition is prefixed by >> (reminiscent of an input condition)
  169. and the conditional expression is enclosed in parentheses. Any valid
  170. conditional expression that may be written at the beginning of the body of the
  171. routine is allowed.
  172.    A function postcondition is prefixed by << (reminiscent of an output
  173. condition) and enclosed in parentheses.  Valid expressions are those that may
  174. be expressed prior to every possible return path from the function. Besides
  175. using the variables that are accessible in the scope of the function, an OLD
  176. specification may be used, to mean the value that a certain expression had
  177. upon entering the routine.
  178.     We will refer to some examples describing some of the member functions of
  179. the following (simplified) class:
  180.  
  181. typedef void* word; 
  182.  
  183. class WList
  184. {
  185.                             /* A general container with list-like
  186.                             capabilities, implemented as a dynamic array
  187.                             of elements of the same type.
  188.                             It can contain elements that fit in a word. */
  189. friend class WList_iterator;
  190.  
  191. public:
  192.     WList();
  193.     void    append(word, int);
  194.     void    insert(int, word, int);
  195.     ...
  196.     void    remove(int, int);
  197.     int     length();
  198.     word&   operator[](int);
  199.     int     invariant()         {return len>=0 && len<=size);}
  200.  
  201. protected:
  202.     short int   len;
  203.     short int   size;
  204.     word*       root;      /* pointer to a vector of words in the free-store */
  205.  
  206. private:
  207.     word*   resize(int);
  208.     void    grow_or_create_maybe(int);
  209.     void    shrink_or_delete_maybe(int);
  210.     void    shift_forward_from(int);
  211.     void    shift_backward_after(int);
  212. };
  213.  
  214.    Memory is allocated in chunks of fixed size, when needed. It is worth
  215. noting at this point the invariant of the class, that asserts the fundamental
  216. property that relates the length of an object (i.e. the number of elements
  217. stored) and its size (the allocated storage). If an "invariant" function is
  218. not defined for a class, and invariant checking is activated, a global
  219. "invariant" function (returning always 1) will be called.
  220.  
  221. The following examples should explain the admissable syntax for function pre
  222. and post conditions:
  223.  
  224. word* WList::resize(int newsize)
  225.  
  226.     >>(root != 0 && size > 0 && newsize > 0)
  227.  
  228.     <<(newptr != 0 && *newptr == OLD(*root))
  229. {
  230.     word* newptr;
  231.     newptr = new word[newsize];
  232.     word* p; word* q;
  233.     int min = (size<newsize) ? size : newsize;
  234.     for(p=root, q=newptr; p-root < min; p++, q++) *q=*p;
  235.     return newptr;
  236. }
  237.  
  238. void WList::grow_or_create_maybe(int stp)
  239.  
  240.     >>(    (root != 0 && size > 0)   || 
  241.         (root == 0 && size == 0) )
  242.  
  243.     <<( root != 0 && size >= OLD(size) )
  244. {
  245.     if( root == 0 ) {                        
  246.                             /* Create!  */
  247.         root = new word[stp];
  248.         size=stp;
  249.     }
  250.     else if( len+1 > size ) {
  251.                             /* Space too small => resize */
  252.         root = resize(size+stp);
  253.         size+=stp;
  254.     }
  255.     len++;
  256. }
  257.  
  258.    Given the previous definition of the function "resize" the preprocessor produces 
  259. C++ code, that (omitting the #line directives and other details) looks like this:
  260.  
  261. #define CONTEXT__ "word* WList::resize(int newsize)"
  262.  
  263. word* WList::resize(int newsize)
  264. {
  265.     PRECONDITION(root != 0 && size > 0 && newsize > 0)
  266.     DECLARE_OLDS( \
  267.     typeof(*root) old__root = *root; \
  268.     )
  269.     word* newptr;
  270.     newptr = new word[newsize];
  271.     word* p; word* q;
  272.     int min = (size<newsize) ? size : newsize;
  273.     for(p=root, q=newptr; p-root < min; p++, q++) *q=*p;
  274.     {
  275.     POSTCONDITION(newptr != 0 && *newptr == old__root)
  276.     INVARIANT()
  277.     return newptr; }
  278. }
  279.  
  280.    The macros are defined in an include file "globals/ASSERTIONS.h" in such a
  281. way to be expanded as follows if the corresponding condition checking is
  282. requested:
  283.  
  284. #define PRECONDITION( CONDITION ) \
  285.     if( !(CONDITION) ) badNews(NOT_PRECOND, CONTEXT__);
  286.  
  287. #define DECLARE_OLDS( OLDS ) \
  288.     OLDS 
  289.  
  290. #define INVARIANT() \
  291.     if( !invariant() ) badNews(NOT_INVAR, CONTEXT__);
  292.  
  293. #define POSTCONDITION( CONDITION ) \
  294.     if( !(CONDITION) ) badNews(NOT_POSTCOND, CONTEXT__); \
  295.     INVARIANT();
  296.  
  297.    One has separate control on each case defining the macro names:
  298.        CHECK_PRECONDITION CHECK_POSTCONDITION CHECK_INVARIANT 
  299.    If these macros are not defined the corresponding condition checking macro
  300. is expanded in a blank, and therefore no code is generated.
  301.    If some assertion checking is activated and an assertion fails to be
  302. verified, the function "badNews" is called. In the simplest case this function
  303. limits itself to printing some error information along with the name of the
  304. function where the failure has been detected (this is passed in the variable
  305. CONTEXT__), but in general it may do better if an exception handling procedure
  306. is defined, as we will see later.
  307.  
  308.  
  309. Support for parametric classes.
  310.  
  311.    Given the previous definition of the container class WList, a parametric
  312. interface to it may be provided to use the container for different kind of
  313. objects, following the scheme at par. 7.3.5 in [1]. The generic interface
  314. class may be written in dependence of a parameter type <T> as follows:
  315.  
  316. #typedef
  317. #include "containers/WList.h"
  318.  
  319. extern int      step<T>;
  320. extern void     initializeWList<T>(int);
  321.  
  322. struct WList<T> : WList 
  323. {
  324.                             /* A generic parameterized container 
  325.                             with list-like capabilities, implemented as a 
  326.                             dynamic array of elements of type <T>.
  327.                             Elements of type <T> must fit in a word. */
  328. friend class WList_iterator<T>;
  329.  
  330.     WList<T>() : WList() {}
  331.     void    append(<T> w) 
  332.                 {WList::append((word)w, step<T>);}
  333.     void    insert(int i, <T> w)
  334.                 {WList::insert(i, (word)w, step<T>);}
  335.     ...        ...
  336.     void    remove(int i)
  337.                 {WList::remove(i, step<T>);}
  338.     int     length()
  339.                 {return WList::length();}
  340.     <T>&    operator[](int i)
  341.                 {return (<T>&) WList::operator[](i);}
  342. };
  343.  
  344.  
  345.    Note the "#typedef" keyword, that will be used later in the instantiation
  346. of this parametric definition, and the use of generic identifiers, depending
  347. on the parameter <T>.
  348.    This generic class may be used as follows:
  349.  
  350.  
  351. #include <String.h>
  352. #include "generics/WList<String*>.h"
  353.  
  354. struct Sentence : WList<String*>
  355. {
  356.     Sentence();
  357.     void input_sentence();
  358.     void print_sentence();
  359.     ...
  360. private:
  361.     void print_current(String*);
  362. };
  363.  
  364.  
  365. Sentence::Sentence() : WList<String*>() {};
  366.  
  367. void Sentence::input_sentence() 
  368. {
  369.     String* s;
  370.     do {
  371.         ...
  372.         append(s);
  373.  
  374.     } while(*s != ".");
  375.     remove(len-1);
  376. }
  377.  
  378.    Parametric classes may therefore be instantiated to a particular type and
  379. parametric identifiers may be used. An arbitrary type (given certain
  380. constraints) may be used and placed between "<...>" in all the places where a
  381. match with the corresponding generic parameter <T> in the generic class is
  382. possible.
  383.    In order to get this working no manual operation is necessary, the
  384. preprocessor of EC++ will take care of all that is needed:
  385.  
  386.    1) First of all the conventional expressions of a parametric identifier
  387. must be expanded in the source files that use parametric classes, in order to
  388. obtain valid C++ identifiers for the parametric names.  In our case the source
  389. files are transformed this way:
  390.  
  391.  
  392. #include <String.h>
  393. #include "generics/WListString_.H"
  394.  
  395. struct Sentence : WListString_
  396. {
  397.     Sentence();
  398.     void input_sentence();
  399.     void print_sentence();
  400.     ...
  401. private:
  402.     void print_current(String*);
  403. };
  404.  
  405.  
  406. Sentence::Sentence() : WListString_() {};
  407.  
  408. void Sentence::input_sentence() 
  409. {
  410.     String* s;
  411.     do 
  412.     {
  413.         ...
  414.         append(s);
  415.  
  416.     } while(*s != ".");
  417.     remove(len-1);
  418. }
  419.  
  420.  
  421. The substring "String_" take the place of "<String*>" in the parametric names,
  422. and is concatenated with the rest of the name.
  423.    As is shown in the example, besides simple types, pointer types may
  424. constitute valid parameter types for EC++. In the actual implementation more
  425. complex parameter type will require a typedef statement, but these should be
  426. quite uncommon cases.
  427.  
  428.    2) We have to generate the files that define the required instance of the
  429. generic class. In the previous example these files are
  430. "generics/WListString_.h" and "generics/WListString_.cc" (the suffixes .H and
  431. .C will then placed by the final preprocessing step). In our example the code
  432. of the instantiated interface class, generated by the preprocessor, is:
  433.  
  434.  
  435. // WListString_.h:
  436.  
  437. typedef String* String_;
  438. #include "containers/WList.h"
  439. extern int      stepString_;
  440. extern void     initializeWListString_(int);
  441.  
  442. struct WListString_ : WList 
  443. {
  444.                             /* A container with list-like capabilities, 
  445.                             implemented as a dynamic array of elements 
  446.                             of type String_.
  447.                             Type String_ must fit in a word. */
  448. friend class WList_iteratorString_;
  449.  
  450.     WListString_() : WList() {}
  451.     void     append(String_ w) 
  452.                 {WList::append((word)w, stepString_);}
  453.     void     insert(int i, String_ w)
  454.                 {WList::insert(i, (word)w, stepString_);}
  455.     ...        ...
  456.     void     remove(int i)
  457.                 {WList::remove(i, stepString_);}
  458.     int     length()
  459.                 {return WList::length();}
  460.     String_&     operator[](int i)
  461.                 {return (String_&) WList::operator[](i);}
  462. };
  463.  
  464.  
  465. // WListString_.cc:
  466.  
  467. #include "WListString_.h"
  468. int      stepString_;
  469. void     initializeWListString_(int step) {stepString_ = step;}
  470.  
  471.  
  472.     To automatically produce these files, the EC++ preprocessor edits the
  473. makefiles that perform the preprocessing and compilation steps in the
  474. directory where the generic class is defined. The user should initially
  475. provide only the basic pattern (which is part of the EC++ distibution) for the
  476. makefiles in this directory.
  477.     Processing a file that uses a parametric class, in case a new instance is
  478. required, EC++ adds new targets to the makefiles, and instructions to produce
  479. the new files via text substitution from the original generic class
  480. definition.
  481.    In order to make the process of getting an executable system coherent, it
  482. is assumed, using EC++, that the make process is split into two steps: a
  483. preprocess step that eventually generates some new instances and produces all
  484. the necessary transformations on the original source files, and a final
  485. compilation and linking step.
  486.  
  487.  
  488. Support for exception handling.
  489.  
  490.    EC++ provides a mechanism for dealing with exceptions. The following are
  491. considered exceptions:
  492.  1) failed assertions;
  493.  2) hardware or system generated signals;
  494.  3) detection, in the user code, of an abnormal condition that cause an
  495. explict call to the "Help!" facility.  
  496.    The construct "Help!" is reserved and it causes execution of the code of
  497. the active "rescue clause".
  498.    In EC++ a rescue clause may be specified after the declaration and prior to
  499. the body of a function (like assertions). It consists of a block of statements
  500. that could be allowed at the beginning of the function body. 
  501.    Let us suppose we have a certain number of functions for which a rescue
  502. clause has been specifed. During program execution the active rescue clause is
  503. the last seen in the stack. If at a certain point an exception is detected,
  504. arising any of the exceptional conditions above, then control is transferred
  505. to the code of the actual active rescue clause. This code may specify also a
  506. program exit, if the situation is considered unrecoverable. If no tranfer
  507. of control is specified, normally the routine body is reentered from the
  508. beginning, and execution is retried. 
  509.    The active rescue clause may not belong to the current executing function,
  510. therefore an exception may involve unraveling the stack until the function
  511. where the rescue is specified.  This may be dangerous, it involves a longjmp
  512. and is accompanied by the problem of the destructors that will not be called.
  513. The standard assumption of EC++, however, is that automatic garbage collection
  514. is active, and this virtually eliminate the destructors problem. In any case,
  515. very often the exception cannot be resolved locally in the function where it
  516. is detected, so this feature is necessary in many situations.
  517.    The following are two very simple examples of the admissable syntax and use
  518. of the rescue clause and help invocation (they don't intend to suggest doing
  519. things this way, but are just illustrative):
  520.  
  521.  
  522. extern double inverse(double x);
  523.  
  524. main()
  525.     rescue:3
  526.     {}
  527. {
  528.     double x;
  529.     cin>>x;
  530.     cout<<"\n"<<inverse(x)<<"\n";
  531. }
  532.  
  533.  
  534. double inverse(double x)
  535.     >>(x!=0)
  536. {
  537.     return 1/x;
  538. }
  539.  
  540.  
  541.    In this example an exception will be detected by the precondition assertion
  542. of the function "inverse" if 0 is given as input. The effect will be to
  543. unravel the function from the stack and execute the rescue clause of the main,
  544. which in this case does nothing besides reinitiating the program and asking
  545. for a new input value.
  546.    The integer number after the colon following the rescue keyword is the
  547. number of times that rescue is allowed. If the program doesn't come out with a
  548. correct situation after retrying that number of times the program will exit
  549. reporting a failure (i.e. an error condition).
  550.    This is (approximately) the code that we get in output from the translator:
  551.  
  552.  
  553. extern double inverse(double x);
  554.  
  555. #define CONTEXT__ "main()"
  556.  
  557. main()
  558. {
  559.     jmp_buf rescue_code;
  560.     Rescue(&rescue_code, 3);
  561.     if(setjmp(rescue_code))
  562.     {}
  563.  
  564.     double x;
  565.     cin>>x;
  566.     cout<<"\n"<<inverse(x)<<"\n";
  567. }
  568.  
  569.  
  570. #define CONTEXT__ "double inverse(double x)"
  571.  
  572. double inverse(double x)
  573. {
  574.     PRECONDITION(x!=0)
  575.  
  576.     return 1/x;
  577. }
  578.  
  579.    The class "Rescue" supports the required operations in conjunction with the
  580. function "badNews":
  581.  1) Pointers to jump locations of rescue instructions are maintained in a
  582. stack list.
  583.  2) A global variable "rescue__" points to the top of the stack and represent
  584. the active rescue clause at any given moment.
  585.  3) If a rescue clause is active (i.e. the Rescue stack is not empty) and an
  586. exception occurs, the function badNews calls for a longjmp to the rescue code.
  587.  4) The number of residual chances after a rescue is executed is updated.
  588.  
  589.    In the second example the rescue clause is local to the routine "inverse"
  590. and is invoked explicitly by the "Help!" instruction.
  591.  
  592. main()
  593. {
  594.     double x;
  595.     cin>>x;
  596.     cout<<"\n"<<inverse(x)<<"\n";
  597. }
  598.  
  599.  
  600. double inverse(double x)
  601.     rescue:1{x=1e-30;}
  602. {
  603.     if(x==0) Help!;
  604.     return 1/x;
  605. }
  606.  
  607.    An exception always involves a call to "badNews". It is worthwhile to point
  608. out that, besides being raised by assertion checking and explicit "Help!"
  609. invocation, an exception may be caused by an error detected by the hardware or
  610. the operating system. This usually causes a signal to be generated, e.g.
  611. floating point exceptions or segmentation violation signals.  The default
  612. initialization phase of an EC++ application makes provisions to trap these
  613. signals at any time and therefore also in these cases an exception is detected
  614. and the function "badNews" is called.
  615.     The method is relatively simple but with this basic framework more complex
  616. behaviors, when needed, can also be simulated. Extension of this mechanism, in
  617. order to make it more flexible, could be considered depending on practical
  618. experience indications.
  619.  
  620.  
  621. Support for garbage collection.
  622.  
  623.    EC++ may be used without garbage collection, but in the kind of application
  624. it is intended for, as well as for avoiding some of the difficulties
  625. encountered in exception handling and in writing rescue clauses, I suggest
  626. using it, and writing code making the assumption that garbage collection is
  627. active. The Boehm's garbage collector [4] is used and therefore the support
  628. provided by EC++ is just a couple of files to interface the Boehm's package
  629. with C++.
  630.    It is worth noting that, using automatic garbage collection, a very
  631. different style of programming can be adopted. All the complications of memory
  632. management that we encounter when we need multiple reference to the same
  633. object and that usually require manual reference counting suddenly disappear.
  634. Also the difference in semantics of assignment and initialization, that took a
  635. chapter in the Stroustrup's book [1], virtually disappears and we can use
  636. pointer assignment without troubles.
  637.  
  638.  
  639. Some notes on the environment.
  640.  
  641.    EC++ assumes work is in a directory tree with a root that is passed to the
  642. EC++ preprocessor as a parameter. Files are searched in this tree. The
  643. following assumptions are also either necessary or recommended:
  644.  1) Original source files have suffixes ".cc" and ".h".
  645.  2) Each directory that contains source files has two separate makefiles:
  646. "preprocess.mk" and "compile.mk". The former applies the preprocessor to the
  647. original source files and produces the corresponding processed files with
  648. suffixes ".C" and ".H". These files contain #line directives to the original
  649. files.
  650.  3) To produce an executable file or a set of coherent object files, all the
  651. preprocess makefiles are executed first in all directories, in order to have
  652. all the include files preprocessed and updated, as well as all instances of
  653. generic classes generated, then the compilations are performed.
  654.    EC++ uses the "typeof" feature, that I think is not yet implemented in AT&T
  655. 2.0, but that is available in GNU g++. Therefore use of g++, or of other C++
  656. compilers (if any) with equivalent features, is assumed.
  657.    The current implementation of EC++ also uses classes of the GNU g++
  658. library.  You should therefore have it available in order to compile and load
  659. the files of the EC++ distribution.
  660.  
  661.  
  662.  
  663. POSSIBLE FUTURE DEVELOPMENTS.
  664.  
  665.    EC++ is currently being used and tested in this implementation. Some future
  666. development (as we can foresee at this time) may include support for:
  667.  1) debug instructions and assertion checking in any needed location;
  668.  2) atomic allocation [4] to improve garbage collection performance;
  669.  3) a more flexible exception handling mechanism;
  670.  4) automatic production of documentation.
  671.  
  672.  
  673.  
  674. REFERENCES.
  675.  
  676. [1] - B. Stroustrup, "The C++ Programming Language", Addison-Wesley, 1986. 
  677. [2] - B. Meyer, "Object-Oriented Software Construction", Prentice-Hall, 1988.
  678. [3] - B. Stroustrup, "Possible Directions for C++", Proc. USENIX, Nov. 1987. 
  679. [4] - H.J. Boehm, "Garbage Collection in an Uncooperative Environment",
  680.       Software Practice and Experience, Vol. 18, pp. 807-820, Sept. 1988. 
  681. [5] - D. Lea, "User's Guide to GNU C++ Library", May 1989.
  682.